﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Net;
using System.Security.Cryptography;

namespace NTUTMessagerAPI
{
    public class SocketAPI
    {
        static int LISTEN_PORT = 8585;
        static int CONNECT_PORT = 0;

        public delegate void ConnectListChange();
        public ConnectListChange ConnectListChangeEvent;
        public delegate void ConnectListAdd(ConnectPair addPair);
        public ConnectListAdd ConnectListAddEvent;
        public delegate void ConnectListRemove(ConnectPair removePair);
        public ConnectListRemove ConnectListRemoveEvent;


        public delegate void GetMessage(ConnectPair pair, String message);
        public GetMessage GetMessageEvent;
        public delegate void GetCommand(ConnectPair pair, String command);
        public GetCommand GetCommandEvent;
        public delegate void GetBytes(ConnectPair pair, byte[] dataByte);
        public GetBytes GetByteEvent;


        public List<ConnectPair> connectedList = new List<ConnectPair>();
        private TcpListener TCP_Listener;

        private ConnectPair.MessageState receiveDataType = ConnectPair.MessageState.getMessage;

        private int _dataSize;
        private List<byte> _fileBuffer;


        public IPAddress MyIp
        {
            get
            {
                IPAddress[] localIPs = Dns.GetHostAddresses(Dns.GetHostName());

                String MyIp = localIPs[0].ToString();
                for (int i = 0; i < localIPs.Length; i++)
                {
                    //Console.WriteLine("ip[" + i + "] = " + (localIPs[i].AddressFamily == AddressFamily.InterNetwork));
                            
                    if (localIPs[i].AddressFamily == AddressFamily.InterNetwork)
                    {
                        MyIp = localIPs[i].ToString();
                    }
                }

                return IPAddress.Parse(MyIp);
            }
        }

        //server connect function
        public void BeginListen() 
        {
      
            TCP_Listener = new TcpListener(MyIp, LISTEN_PORT);
            TCP_Listener.Start();
            TCP_Listener.BeginAcceptTcpClient(SomeoneConnected,null);

        }

        public void SomeoneConnected(IAsyncResult ar) 
        {
             TcpClient ServerSocket = TCP_Listener.EndAcceptTcpClient(ar);

             Console.WriteLine("[ "+ServerSocket.Client.RemoteEndPoint+" Accept Connected ]");
             ConnectPair pair = new ConnectPair();
             pair.socket = ServerSocket.Client;

             pair.socket.BeginReceive(pair.DataReceived,SocketFlags.None,ReceiveSomething,pair);

             TCP_Listener.BeginAcceptTcpClient(SomeoneConnected, null);

             AddConnect(pair);
        }

        public void EndListen() 
        {
            TCP_Listener.Stop();
        }
        // server conect funtion


        // client connect funtion
        public void ConnectTo(String address)
        {
            ConnectTo(IPAddress.Parse(address));
        }

        public void ConnectTo(IPAddress address) 
        {
            TcpClient ClientSocket;

            Console.WriteLine("[My Ip :" + MyIp+"]");
            Console.WriteLine("[Try to Connect to " + address + " Port " + LISTEN_PORT+"]");
            
            ClientSocket = new TcpClient(new IPEndPoint(MyIp, CONNECT_PORT));
            ClientSocket.BeginConnect(address, LISTEN_PORT, ConnectSuccess, ClientSocket);

          }

        public void ConnectSuccess(IAsyncResult result)
        {
            TcpClient ClientSocket = (TcpClient)result.AsyncState;

            Console.WriteLine("["+ClientSocket.Client.RemoteEndPoint + " Connect Success !!]");

            ConnectPair pair = new ConnectPair();
            pair.socket = ClientSocket.Client;

            pair.socket.BeginReceive(pair.DataReceived, SocketFlags.None, ReceiveSomething, pair);

            AddConnect(pair);
        }
        // client connect funtion

        
        

        //send and receive
        public void Send_Diffie_Hellman(int index, int key)
        {
            connectedList[index]._Diffie_Hellman.PrivateKey = key;
            connectedList[index].encrypt = ConnectPair.EncryptState.Key;
            connectedList[index].Send(BitConverter.GetBytes((int)ConnectPair.MessageState.setDiffieHellman));
            connectedList[index].Send(BitConverter.GetBytes(BitConverter.GetBytes(connectedList[index]._Diffie_Hellman.PublicKey).Length));
            connectedList[index].Send(BitConverter.GetBytes(connectedList[index]._Diffie_Hellman.PublicKey));
        }

        public void Send_Diffie_Hellman(ConnectPair pair, int key)
        {
            pair._Diffie_Hellman.PrivateKey = key;
            pair.encrypt = ConnectPair.EncryptState.Key;
            pair.Send(BitConverter.GetBytes((int)ConnectPair.MessageState.setDiffieHellman));
            pair.Send(BitConverter.GetBytes(BitConverter.GetBytes(pair._Diffie_Hellman.PublicKey).Length));
            pair.Send(BitConverter.GetBytes(pair._Diffie_Hellman.PublicKey));
        }

        public void Send_Diffie_Hellman(IPAddress address, int key)
        {
            int index = -1;

            for (int i = 0; i < connectedList.Count; i++)
            {
                if (connectedList[i].address.Contains(address.ToString()))
                {
                    index = i;
                    break;
                }
            }

            if (index >= 0)
            {
                Send_Diffie_Hellman(index, key);
            }
            else
            {
                Console.WriteLine("沒有連過....");
            }
        }


        public void SendMessage(int index, String Message) 
        {
            Console.WriteLine("\n----To :" + connectedList[index].address + "----");
            Console.WriteLine(Message);
            Byte[] byteArray = System.Text.Encoding.UTF32.GetBytes(Message);
            connectedList[index].Send(BitConverter.GetBytes((int)ConnectPair.MessageState.getMessage));
            connectedList[index].Send(BitConverter.GetBytes(byteArray.Length));
            connectedList[index].Send(byteArray);

            
           
            Console.WriteLine("----To :" + connectedList[index].address + "----");
        }

        public void SendMessage(ConnectPair pair, String Message)
        {
            Console.WriteLine("\n----To :" + pair.address + "----");
            Console.WriteLine(Message);
            Byte[] byteArray = System.Text.Encoding.UTF32.GetBytes(Message);
            pair.Send(BitConverter.GetBytes((int)ConnectPair.MessageState.getMessage));
            pair.Send(BitConverter.GetBytes(byteArray.Length));
            pair.Send(byteArray);

           
            
            Console.WriteLine("----To :" + pair.address + "----");
        }

        public void SendMessage(IPAddress address, String Message)
        {
            int index = -1;

            for (int i = 0; i < connectedList.Count;i++ )
            {
                if (connectedList[i].address.Contains(address.ToString()))
                {
                    index = i;
                    break;
                }
            }

            if (index >= 0)
            {
                SendMessage(index, Message);
            }
            else 
            {
                Console.WriteLine("沒有連過....");
            }

        }


        public void SendBytes(int index, Byte[] dataByte)
        {
            connectedList[index].Send(BitConverter.GetBytes((int)ConnectPair.MessageState.getByteData));
            connectedList[index].Send(BitConverter.GetBytes(dataByte.Length));
            connectedList[index].Send(dataByte);
        }

        public void SendBytes(ConnectPair pair, Byte[] dataByte)
        {
            pair.Send(BitConverter.GetBytes((int)ConnectPair.MessageState.getByteData));
            pair.Send(BitConverter.GetBytes(dataByte.Length));



            List<Byte> list = new List<Byte>();

            for (int i = 0; i < dataByte.Length; i++) 
            {
                list.Add(dataByte[i]);

                if (list.Count >= 4096) 
                {
                    pair.Send(list.ToArray());

                    System.Threading.Thread.Sleep(1000);

                    Console.WriteLine("byte:"+i);

                    list.Clear();
                }
            }

            if (list.Count > 0)
            {
                System.Threading.Thread.Sleep(1000);

                pair.Send(list.ToArray());
                Console.WriteLine("剩下的:" + list.Count);
                list.Clear();
            }


        }

        public void SendBytes(IPAddress address, Byte[] dataByte)
        {
            int index = -1;

            for (int i = 0; i < connectedList.Count; i++)
            {
                if (connectedList[i].address.Contains(address.ToString()))
                {
                    index = i;
                    break;
                }
            }

            if (index >= 0)
            {
                SendBytes(index, dataByte);
            }
            else
            {
                Console.WriteLine("沒有連過....");
            }
        }


        public void SendCommand(int index, String Command)
        {
            Console.WriteLine("\n----To :" + connectedList[index].address + "----");
            Console.WriteLine(Command);
            Byte[] byteArray = System.Text.Encoding.UTF32.GetBytes(Command);
            connectedList[index].Send(BitConverter.GetBytes((int)ConnectPair.MessageState.getCommand));
            connectedList[index].Send(BitConverter.GetBytes(byteArray.Length));
            connectedList[index].Send(byteArray);

           
            
            Console.WriteLine("----To :" + connectedList[index].address + "----");
        }

        public void SendCommand(ConnectPair pair, String Command)
        {
            Console.WriteLine("\n----To :" + pair.address + "----");
            Console.WriteLine(Command);
            Byte[] byteArray = System.Text.Encoding.UTF32.GetBytes(Command);
            pair.Send(BitConverter.GetBytes((int)ConnectPair.MessageState.getCommand));
            pair.Send(BitConverter.GetBytes(byteArray.Length));
            pair.Send(byteArray);


            
            
            Console.WriteLine("----To :" + pair.address + "----");
        }

        public void SendCommand(IPAddress address, String Command)
        {
            int index = -1;

            for (int i = 0; i < connectedList.Count; i++)
            {
                if (connectedList[i].address.Contains(address.ToString()))
                {
                    index = i;
                    break;
                }
            }

            if (index >= 0)
            {
                SendCommand(index, Command);
            }
            else
            {
                Console.WriteLine("沒有連過....");
            }
        }

        public void ReceiveSomething(IAsyncResult result)
        {
            bool Error = false;
            ConnectPair pair = (ConnectPair)result.AsyncState;
            try
            {  
                String data = "From :" + pair.address;                
                Error = !pair.socket.Connected;
            }
            catch 
            {
                Error = true;
                RemoveConnect(pair);
                Console.WriteLine("Someone Leaved....");
            }

            try
            {  
                    if (!Error)
                    {
                        ProccessDecryption(pair);

                        ProcessData(pair);
                    }
                    else
                    {
                        Console.WriteLine("Receive Error!!!!");
                    }
            }
            catch
            {
                Console.WriteLine("Pair Failed");
                RemoveConnect(pair);
            }
        }

        private void ProccessDecryption(ConnectPair pair) 
        {
            if (pair.encrypt == ConnectPair.EncryptState.AES)
            {
                byte[] byteData = pair.getByteArray();

                byte[] decrypt = AES_API.AESDecoder(byteData, pair._Diffie_Hellman.ShareSecret);
                pair.SetByte(decrypt);
            }
            else if (pair.encrypt == ConnectPair.EncryptState.RSA) 
            {
                byte[] byteData = pair.getByteArray();

                if (byteData.Length < 128)
                {
                    Console.WriteLine("this message is not encrypted....");
                    return;
                }

                byte[] decrypt = RSA_API.RSADecrypt(byteData, pair.Encrypt_Parameter, false);
                pair.SetByte(decrypt);
            }
        }

        private void ProcessData(ConnectPair pair)
        {
            if (pair.state == ConnectPair.MessageState.getReceiveType)
            {
                Console.WriteLine("\n----From :" + pair.address+"----");

                receiveDataType = (ConnectPair.MessageState)BitConverter.ToInt32(pair.getByteArray(), 0);
                //Console.WriteLine("receive type = " + receiveDataType);

                if (receiveDataType == ConnectPair.MessageState.RSAClearByte)
                {
                    pair.SetByte(128);
                    pair.state = ConnectPair.MessageState.getReceiveType;
                  
                    Console.WriteLine("RSA");
                    Console.WriteLine("----From :" + pair.address + "----");

                    pair.encrypt = ConnectPair.EncryptState.RSA;
                }
                else
                {
                    pair.SetByte(4);
                    pair.state = ConnectPair.MessageState.getSize;
                }
            }
            else if (pair.state == ConnectPair.MessageState.getSize)
            {
                _dataSize = BitConverter.ToInt32(pair.getByteArray(), 0);

                //Console.WriteLine("MessageSize = " + _dataSize);

                _fileBuffer = new List<byte>();

                if (_dataSize > 4096)
                {
                    pair.SetByte(4096);
                    _dataSize -= 4096;
                }
                else
                {
                    pair.SetByte(_dataSize);
                    _dataSize = 0;
                }

                pair.state = receiveDataType;
            }
            else if (pair.state == ConnectPair.MessageState.getMessage)
            {
                String message = System.Text.UTF32Encoding.UTF32.GetString(pair.getByteArray());
                Console.WriteLine(message);
                if (GetMessageEvent != null)
                {
                    GetMessageEvent.Invoke(pair, message);
                }

                Console.WriteLine("----From :" + pair.address + "----");
                pair.SetByte(4);
                pair.state = ConnectPair.MessageState.getReceiveType;
            }
            else if (pair.state == ConnectPair.MessageState.getCommand)
            {
                String command = System.Text.UTF32Encoding.UTF32.GetString(pair.getByteArray());
                Console.WriteLine("command " + command);

                if (GetCommandEvent != null)
                {
                    GetCommandEvent.Invoke(pair, command);
                }
                Console.WriteLine("----From :" + pair.address + "----");
                pair.SetByte(4);
                pair.state = ConnectPair.MessageState.getReceiveType;

            }
            else if (pair.state == ConnectPair.MessageState.getByteData)
            {
                byte[] byteData = pair.getByteArray();
                foreach (byte b in byteData)
                {
                    _fileBuffer.Add(b);
                }


                Console.WriteLine(_fileBuffer.Count);
                // Console.ReadLine();


                if (_dataSize != 0)
                {
                    if (_dataSize > 4096)
                    {

                        pair.SetByte(4096);
                        _dataSize -= 4096;
                    }
                    else
                    {

                        pair.SetByte(_dataSize);
                        _dataSize = 0;

                        Console.WriteLine("--接收完畢--");
                        Console.WriteLine("----From :" + pair.address + "----");
                    }

                    
                    pair.state = ConnectPair.MessageState.getByteData;
                }
                else
                {
                    if (GetByteEvent != null)
                    {
                        Console.WriteLine("--接收完畢--");
                        Console.WriteLine("----From :" + pair.address + "----");
                        GetByteEvent.Invoke(pair, _fileBuffer.ToArray());
                    }

                    pair.SetByte(4);
                    pair.state = ConnectPair.MessageState.getReceiveType;

                }
            }
            else if (pair.state == ConnectPair.MessageState.setDiffieHellman) 
            {
                int anotherPublicKey = BitConverter.ToInt32(pair.getByteArray(), 0);

                pair._Diffie_Hellman.OtherPublicKey = anotherPublicKey;

                Console.WriteLine("Public Key = " + pair._Diffie_Hellman.OtherPublicKey);
                Console.WriteLine("----From :" + pair.address + "----");

                if (pair.encrypt != ConnectPair.EncryptState.Key)
                {
                    

                    pair._Diffie_Hellman.PrivateKey = Diffie_hellman._random.Next(100);
                    Send_Diffie_Hellman(pair, pair._Diffie_Hellman.PublicKey);

                    Console.WriteLine("\n----To :" + pair.address + "----");
                    Console.WriteLine("Public Key : "+ pair._Diffie_Hellman.PublicKey);
                    Console.WriteLine("----To :" + pair.address + "----");
                }
                            

                Console.WriteLine(" [ Diffie Hellman Key Distribution ]");
                Console.WriteLine(" [ 我方的　public key : " + pair._Diffie_Hellman.PublicKey+" ]");
                Console.WriteLine(" [ 我方的　private key : " + pair._Diffie_Hellman.PrivateKey + " ]");
                Console.WriteLine(" [ 對方的　public key : " + pair._Diffie_Hellman.OtherPublicKey + " ]");
                Console.WriteLine(" [ 我們的  share secret : " + pair._Diffie_Hellman.ShareSecret + " ]");
                Console.WriteLine(" [ Diffie Hellman Key Distribution ]");

                Console.WriteLine("");
                Console.WriteLine("{ Set Connction : \"" + pair.address + "\" : AES : Key = " + pair._Diffie_Hellman.ShareSecret+" }");

                pair.encrypt = ConnectPair.EncryptState.AES;

                pair.SetByte(4);
                pair.state = ConnectPair.MessageState.getReceiveType;
            }
            


            try
            {


                pair.socket.BeginReceive(pair.DataReceived, SocketFlags.None, ReceiveSomething, pair);
            }
            catch
            {


                RemoveConnect(pair);
                Console.WriteLine("Someone Leaved....");
            }
        }
        //send and receive





        public void PrintAllPair()
        {
            foreach (ConnectPair p in connectedList)
            {
                Console.WriteLine(p.address);
            }
        }

        public void AddConnect(ConnectPair pair)
        {
            connectedList.Add(pair);

            if (ConnectListChangeEvent != null)
            {
                ConnectListChangeEvent.Invoke();
            }

            if (ConnectListAddEvent != null)
            {
                ConnectListAddEvent.Invoke(pair);
            }
        }

        public void RemoveConnect(ConnectPair pair)
        {
            connectedList.Remove(pair);

            if (ConnectListChangeEvent != null)
            {
                ConnectListChangeEvent.Invoke();
            }

            if (ConnectListRemoveEvent != null)
            {
                ConnectListRemoveEvent.Invoke(pair);
            }
        }


    }

    public class ConnectPair
    {
        public enum MessageState
        {
            getReceiveType,
            getSize,
            getByteData,
            getMessage,
            getCommand,
            setDiffieHellman,
            RSAClearByte,
        }

        public enum EncryptState
        {
            none,
            Key,
            AES,
            RSA,
        }



        public Socket socket;
        public List<ArraySegment<byte>> DataReceived = new List<ArraySegment<byte>>();

        public MessageState state = MessageState.getReceiveType;
        public EncryptState encrypt = EncryptState.none;

        public Diffie_hellman _Diffie_Hellman = new Diffie_hellman();

        //public RSACryptoServiceProvider RSA_Provider = new RSACryptoServiceProvider();
        public RSAParameters Encrypt_Parameter;
        private Byte[] RSAPublicKey ;

        public void setRSA(RSAParameters RSAParameter,String PublicKey)
        {
            Console.WriteLine("---To "+this.address+" ---");
            Console.WriteLine("RSA");
            Console.WriteLine("---To " + this.address + " ---");

            Encrypt_Parameter = RSAParameter;

            //Console.WriteLine("set public key = "+PublicKey);

            RSAPublicKey = RSA_API.StringToByte(PublicKey);
            

            Send(BitConverter.GetBytes((int)ConnectPair.MessageState.RSAClearByte));

         
        }

        public object UserData
        {
            get;
            set;
        }

        public String address
        {
            get
            {
                //ipv6  can't  run!!!!!!!!!!!!!
                return socket.RemoteEndPoint.ToString().Split("%:]".ToCharArray())[0].Trim("[".ToCharArray());
            }
        }

        public bool Connected
        {
            get
            {
                return socket.Connected;
            }
        }
        

        public ConnectPair()
        {
            Initialize();
        }

        public void Send(Byte[] data)
        {
            if (encrypt == EncryptState.AES)
            {
                byte[] encryptData = AES_API.AESEncoder(data, _Diffie_Hellman.ShareSecret);
                socket.Send(encryptData);
            }
            else if (encrypt == EncryptState.RSA)
            {
                byte[] encryptData = RSA_API.RSAEncrypt(data, RSAPublicKey, false);
                socket.Send(encryptData);
            }
            else
            {
                socket.Send(data);
            }
        }

        private void Initialize()
        {
            SetByte(4);
        }

        public byte[] getByteArray ()
        {
            return DataReceived[0].Array;
        }

        public void SetByte(int count)
        {
            ArraySegment<byte> FirstData;
            if (encrypt == EncryptState.AES)
            {
                int realCount = (int)Math.Ceiling(count / 16.0f) * 16;

                if (count % 16 == 0) 
                {
                    realCount+=16;
                }
                FirstData = new ArraySegment<byte>(new byte[realCount]);
            }
            else if (encrypt == EncryptState.RSA)
            {
                FirstData = new ArraySegment<byte>(new byte[RSA_API.MeasureByteNumber(count)]);
            }
            else
            {
                FirstData = new ArraySegment<byte>(new byte[count]);
            }


            DataReceived = new List<ArraySegment<byte>>();
            DataReceived.Add(FirstData);
        }

        public void SetByte(Byte[] array)
        {
            DataReceived = new List<ArraySegment<byte>>();
            ArraySegment<byte> FirstData = new ArraySegment<byte>(array);
            DataReceived.Add(FirstData);
        }



        public override String ToString()
        {
            return "IP:" + address;
        }

    }
}
